﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace GodotAssetLibrarySpider
{
    public class WebClientHelper
    {
        public static WebClient Instance => _instance.Value;
        private static Lazy<WebClient> _instance = new Lazy<WebClient>();
    }

    public class Page
    {
        [XmlIgnore]
        public string PageURL { get; protected set; }

        [XmlIgnore]
        protected string HtmlContent { get; set; }

        public Page()
        {

        }

        public virtual void Load()
        {

        }

        protected void LoadHtmlContent()
        {
            HtmlContent = WebClientHelper.Instance.DownloadString(PageURL);
        }

        protected static string CleanMainBody(string mainBody)
             => ResolveHelper.CleanMainBody(mainBody);


        ///<summary>提取两节点范围内东西</summary>
        protected static string ResolvePairInnerText(string htmlContent, string start, string end)
            => ResolveHelper.ResolvePairInnerText(htmlContent, start, end);


        protected static string ResolvePairInnerTextLast(string htmlContent, string start, string end)
            => ResolveHelper.ResolvePairInnerTextLast(htmlContent, start, end);

    }

    public class Detail : Page
    {
        [XmlAttribute]
        public string Title { get; set; }

        [XmlAttribute]
        public string Version { get; set; }

        [XmlAttribute]
        public string MainBody { get; set; }

        [XmlAttribute]
        public string Tag { get; set; }

        [XmlAttribute]
        public string FileURL { get; set; }

        public Detail()
        {

        }

        public Detail(string pageURL)
        {
            PageURL = pageURL;
        }

        public override void Load()
        {
            try
            {
                LoadHtmlContent();

                Title = ResolveDetailTitle(HtmlContent);
                Version = ResolveDetailVersion(HtmlContent);
                MainBody = ResolveDetailMainBody(HtmlContent);
                Tag = ResolveDetailTag(HtmlContent);
                FileURL = ResolveDetailDownloadURL(HtmlContent);

                FileURL = FileURL.Replace("https", "http");

                base.Load();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"pageurl {PageURL} ex {ex.Message}");
                UrlFailRecorder.Log(UrlFailRecorder.ResourceEnum.Detail, PageURL);
            }

        }

        public override string ToString()
        {
            if (string.IsNullOrWhiteSpace(Title))
            {
                return PageURL;
            }
            else
            {
                return $"[{Tag}] {Title} {Version}";
            }

        }

        public void DownloadFile(string localFileName)
        {
            WebClientHelper.Instance.DownloadFile(FileURL, localFileName);
        }

        private static string ResolveDetailTitle(string htmlContent)
        {
            var mediaHeading = "<h4 class=\"media-heading\">";
            var mediaHeadingIndex = htmlContent.IndexOf(mediaHeading);
            var leftArrow = "<small";
            var leftArrowIndex = htmlContent.IndexOf(leftArrow, mediaHeadingIndex);

            var titleLen = leftArrowIndex - mediaHeadingIndex - mediaHeading.Length;
            var title = htmlContent.Substring(mediaHeadingIndex + mediaHeading.Length, titleLen);

            title = title.Trim('\n', '\r', ' ');

            return title;
        }
        private static string ResolveDetailVersion(string htmlContent)
        {
            var smallStart = "<small>";
            var smallEnd = "</small>";

            var result = ResolvePairInnerText(htmlContent, smallStart, smallEnd);
            return result;
        }
        private static string ResolveDetailMainBody(string htmlContent)
        {
            var divStart = "<div class=\"media-body\">";
            var divEnd = "</div>";
            var mediaBody = ResolvePairInnerText(htmlContent, divStart, divEnd);
            var pStart = "<p>";
            var pEnd = "</p>";

            var mainBody = ResolvePairInnerTextLast(mediaBody, pStart, pEnd);

            mainBody = CleanMainBody(mainBody);

            return mainBody;
        }
        private static string ResolveDetailTag(string htmlContent)
        {
            //  <span class="label label-primary">Tools</span>
            var start = "<span class=\"label label-primary\">";
            var end = "</span>";

            return ResolvePairInnerText(htmlContent, start, end);
        }
        private static string ResolveDetailDownloadURL(string htmlContent)
        {
            var start = "<a class=\"btn btn-primary\"";
            var end = "<i class=\"glyphicon glyphicon-download\"></i> Download";
            var raw = ResolvePairInnerText(htmlContent, start, end);

            start = "href=\"";
            end = "\">";

            var result = ResolvePairInnerText(raw, start, end);

            return result;
        }

    }

    public class Home : Page
    {

        public IReadOnlyList<Detail> Details => _details;
        private List<Detail> _details;

        public Home(string pageURL)
        {
            PageURL = pageURL;
        }

        public override void Load()
        {
            try
            {
                LoadHtmlContent();
                ResolveHome(HtmlContent);
                base.Load();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"pageurl {PageURL} ex {ex.Message}");
                UrlFailRecorder.Log(UrlFailRecorder.ResourceEnum.Home, PageURL);
            }

        }

        private void ResolveHome(string htmlContent)
        {
            UrlFailRecorder.FilePath = Path.Combine(Environment.CurrentDirectory, "exe.log");

            var pattern = @"<li\s+class\s*=\s*[""']\s*asset-item\s*[""'].*?>.*?</li>";

            var matches = Regex.Matches(htmlContent, pattern, RegexOptions.Singleline);

            _details = new List<Detail>();

            foreach (var match in matches)
            {
                var detailUrl = ResolveAssetUrl(match.ToString());

                var detail = new Detail(detailUrl);

                _details.Add(detail);
                //Console.WriteLine(detailUrl);
            }
        }

        /*
         <li class="asset-item">
<a href="/asset-library/asset/1599" class="asset-header">
<img class=
         */
        private string ResolveAssetUrl(string htmlContent)
        {
            var ahref = "<a href=\"";
            var assetHeader = "\" class=\"asset-header\">";
            var ahrefIndex = htmlContent.IndexOf(ahref);
            var assetHeaderIndex = htmlContent.IndexOf(assetHeader);

            var startIndex = ahrefIndex + ahref.Length;
            var endIndex = assetHeaderIndex;

            var clipLen = endIndex - startIndex;

            var subUrl = htmlContent.Substring(startIndex, clipLen);
            var detailUrl = "http://godotengine.org" + subUrl;

            return detailUrl;
        }


    }
    public static class UrlFailRecorder
    {
        public enum ResourceEnum
        {
            Home, Detail, DownloadFile,
        }

        public class Record
        {
            public ResourceEnum Type { get; set; }
            public string Url { get; set; }

            public static Record Parse(string str)
            {
                if (String.IsNullOrWhiteSpace(str)) return null;
                var parts = str.Split(',').Select(i => i.Trim());
                var typeStr = parts.ElementAt(0);
                var url = parts.ElementAt(1);

                var type = (ResourceEnum)Enum.Parse(typeof(ResourceEnum), typeStr);
                return new Record() { Type = type, Url = url };
            }

            public override string ToString()
            {
                return $"{Type}, {Url}";
            }
        }

        public static string FilePath { get; set; }

        public static void Log(ResourceEnum type, string url)
        {
            var record = new Record() { Type = type, Url = url };

            using var writter = File.AppendText(FilePath);
            writter.WriteLine(record);
        }

        public static Record Read()
        {
            var fileStream = File.OpenRead(FilePath);
            var reader = new StreamReader(fileStream);
            var line = reader.ReadLine();
            var record = Record.Parse(line);
            if (record == null) return null;
            fileStream.Seek(line.Length + 1, SeekOrigin.Begin);

            using var memoryStream = new MemoryStream();
            fileStream.CopyTo(memoryStream);

            reader.Close(); reader.Dispose();
            fileStream.Close(); fileStream.Dispose();

            fileStream = File.Create(FilePath);
            memoryStream.Seek(0, SeekOrigin.Begin);
            memoryStream.CopyTo(fileStream);

            fileStream.Close(); fileStream.Dispose();

            return record;
        }
    }


    public static class ResolveHelper
    {
        public static string GetHomePageUrl(int pageIndex)
        {
            return $"http://godotengine.org/asset-library/asset?page={pageIndex}&filter=&category=&godot_version=4.3&cost=&sort=updated";
        }

        public static string CleanMainBody(string mainBody)
        {
            mainBody = mainBody.Replace("<br>", String.Empty);
            mainBody = mainBody.Replace("&quot;", "\"");
            return mainBody;
        }

        ///<summary>提取两节点范围内东西</summary>
        public static string ResolvePairInnerText(string htmlContent, string start, string end)
        {
            var smallStart = start;
            var smallEnd = end;

            var smallStartIndex = htmlContent.IndexOf(smallStart);
            var smallEndIndex = htmlContent.IndexOf(smallEnd, smallStartIndex);
            var contentLen = smallEndIndex - smallStartIndex - smallStart.Length;

            var result = htmlContent.Substring(smallStartIndex + smallStart.Length, contentLen);

            return result;
        }


        public static string ResolvePairInnerTextLast(string htmlContent, string start, string end)
        {
            var smallStart = start;
            var smallEnd = end;

            var smallStartIndex = htmlContent.LastIndexOf(smallStart);
            var smallEndIndex = htmlContent.LastIndexOf(smallEnd);
            var contentLen = smallEndIndex - smallStartIndex - smallStart.Length;

            var result = htmlContent.Substring(smallStartIndex + smallStart.Length, contentLen);


            return result;
        }



    }

}
